// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.


Type.registerNamespace('AnimatedSiteMapMenu');

AnimatedSiteMapMenu.AnimatedSiteMapMenuBehavior = function() {
    /// <summary>
    /// AnimatedSiteMapMenu behavior/instance class.
    /// </summary>
    AnimatedSiteMapMenu.AnimatedSiteMapMenuBehavior.initializeBase(this);

    this._urlMap = null;
    this._nodeList = new Array();  // the entire list of related nodes
    this._activeNodes = new Array();  // the active ones ie. the parent in the middle and connected children
    this._pointList = new Array();   // this will store every Dom element that represents a Point, this way it makes it easy to clear the
                                     // lines draw using those points later
    this._clickHandler = null;
    
    this._targetContainer = "";   // We either support the entire screen via an empty or NULL string, 
                                  // or the ID of the target control, typically a DIV
                                  
    this._initialAnimSpeed = 1;     // Animation duration in seconds for initial screen entry/display anim
    this._navigateAnimSpeed = .5;   // Animation duration in seconds for navigation animations (sub nodes)
    this._leafNodeStyle = "leafNode";
    this._centreNodeStyle = "centreNode";
    this._linkStyle = "linkStyle";
    this._backLinkText = "";
    this._backLinkStyle = "";
    this._lineColour = "";
    this._lineThickness = 1;
}

AnimatedSiteMapMenu.AnimatedSiteMapMenuBehavior.prototype = {

    initialize : function() {
        /// <summary>
        /// Initializes the behavior.
        /// </summary>
        AnimatedSiteMapMenu.AnimatedSiteMapMenuBehavior.callBaseMethod(this, 'initialize');

        this._clickHandler = Function.createDelegate(this,this._handleClick);
        this.parseNodeList();     
        this._nodeList[0].set_Selected(true);
        this.constructDomObjects(this._nodeList[0],false);  // construct our DOM objects from the root node only
        
        this.setupAnimations();
    },

    dispose : function() {
        /// <summary>
        /// Standard component/behavior dispose logic. Basically clean up after yourself.
        /// </summary>
        AnimatedSiteMapMenu.AnimatedSiteMapMenuBehavior.callBaseMethod(this, 'dispose');
        
        // Clear all the handlers
        for (var cnt=0; cnt < this._nodeList.length; cnt++)
        {
            var node = this._nodeList[cnt];
            if (node != null)
            {
                var e = node.get_DomElement();
                if (e != null)
                $clearHandlers(e);
            }
            delete node;
        }
        
    },

    parseNodeList : function() {
        /// <summary>
        /// This function takes the string that has been generated from the server, and constructs an array of
        /// AnimatedSiteMapMenu.UrlNode objects (defined later in this code) that represent each node in the
        /// SiteMap heirarchy. The string is basically a serialised set of node items that are separated by a semi-colon ( ; ),
        /// and each item has a set of properties within it which are separated by a pipe character ( | ).
        /// Serialised Format is:
        /// -----------------Node-----------------------|-----------------Node-----------------------|
        /// Title | Url | ParentNodeTitle | HasChildren ; Title | Url | ParentNodeTitle | HasChildren
        /// </summary>
        var nodePairs = this._urlMap.split(';');
        for (var i=0; i < nodePairs.length; i++)
        {
            var nodeProps = nodePairs[i].split('|');
            var tmpNode = new AnimatedSiteMapMenu.UrlNode(this.get_id() + "_node_" + i,nodeProps[0],nodeProps[1],nodeProps[2]);
            if (nodeProps[3] == 'true')
                tmpNode.set_HasChildren(true);
            Array.add(this._nodeList,tmpNode);
        }
    },
    
    constructDomObjects : function(parentUrlNode, centreParent) {
        /// <summary>
        /// Constructs the DOM objects associated with each node of our site map. In addition,
        /// the centre of the container is recalculated, positions of each DOM element setup, and
        /// an active node list (according to what level is currently selected/active) is created.
        /// </summary>
        /// <field name="parentUrlNode" type="AnimatedSiteMapMenu.UrlNode">
        /// The parent node. Initially this is the Home page, but if other nodes are selected that have
        /// children, then they become the parent node.
        /// </field>
        /// <field name="centreParent" type="boolean">
        /// If this flag is true, the parent node is centred within the container by recalculating what
        /// the centre is. If this is false, then the current position of the parent node is used instead.
        /// </field>
        this._activeNodes = new Array();
        
        // Make up an active node list with the parent always at array element 0
        if (parentUrlNode != null && parentUrlNode != "")
            Array.add(this._activeNodes,parentUrlNode);  // always make the parent node 0
            
        for (var i=0; i < this._nodeList.length; i++)
        {
            var tmpNode = this._nodeList[i];
            if (tmpNode.get_ParentNode() == parentUrlNode.get_Title())  // cater 
                Array.add(this._activeNodes,tmpNode);  
        }
        
        var parentPos = new Object();
        if (parentUrlNode != null && parentUrlNode.get_DomElement() != null)
        {
            if (centreParent)
            {
                parentPos = this._getCentreOfContainer();
                var size =  $common.getSize(this._activeNodes[0].get_DomElement());
                parentPos.x -= parseInt(size.width/2);
                parentPos.y -= parseInt(size.height/2);

            } else
                parentPos = $common.getLocation(parentUrlNode.get_DomElement());
        }
        else
        {
            parentPos.x = 0;
            parentPos.y = 0;
        }
        
        // Now construct the Dom Element for our active nodes.
        for (var i=0; i < this._activeNodes.length; i++)
        {
            var tmpNode = this._activeNodes[i];
            if (tmpNode.get_DomElement() == null)
            {
                // create a simple box to represent the link for now
                var e = document.createElement('div');
                e.style.position= "absolute"; 
                e.style.top = parentPos.x + "px";
                e.style.left = parentPos.y + "px";
                
                var linkStyle = "";
                if ((i == 0 && this._centreNodeStyle != "") || (i > 0 && this._leafNodeStyle != "") )
                {
                    if (i == 0)
                    {
                        Sys.UI.DomElement.addCssClass(e,this._centreNodeStyle);
                    }
                    else
                    {
                        Sys.UI.DomElement.addCssClass(e,this._leafNodeStyle);
                    }
                    linkStyle = "class = '" + this._linkStyle + "' "
                } else
                {
                    e.style.backgroundColor = "White";  // yeah some hardcoded styles....we'll fix it later
                    if (i ==0 )
                        e.style.zIndex = 10;
                    else
                        e.style.zIndex = 9;
                        e.style.borderStyle="solid";
                        e.style.borderWidth="1px";
                }
                // NOTE: Format of elements ID is:
                //       UniqueID_NodeTitle_OptionalElement
                e.innerHTML = "<a " + linkStyle + " id='" + tmpNode.get_id() + "' href='" + tmpNode.get_Url() + "'>" + tmpNode.get_Title() + "</a>";
                e.id = this.get_id() + "_" + tmpNode.get_id() + "_div";  // set the surround DIV's ID
                
                tmpNode.set_DomElement(e);
            }
            var e = tmpNode.get_DomElement();
            $addHandler(e,"click",this._clickHandler);
            document.body.appendChild(e);
            // After a fade animation, the DOM element has a remaining opacity value, so if we try and re-add this back to the
            // document, it remains invisible so we need to reset its opacity.
            $common.setElementOpacity(e,100);
            $common.setLocation(e,parentPos);    // for some reason, secondary animations are not positioned correctly when
                                                                                 // created and this explicit positioning fixes that
        }
    }, 
    
    // We can potentially create an event that passes off our created DOM objects ready to be animated to allow 
    // developers to develop custom animations based around the existing node set. Initially, we will do all the
    // animations inside the control. Not sure of the merits of this idea, as it may make the control itself too complex.
    setupAnimations : function() {
        /// <summary>
        /// This function sets up the animation objects based on what the current selectioin is and prepares the fade and
        /// movement of each node. The circular positioning is also calculated here.
        /// </summary>

        // Assume the circular type menu for now
        var destinationAnims = new Array();
        
        // Make the root node (ie. position 0 in array) the center
        var centrePos = this._getCentreOfContainer();
        var size =  $common.getSize(this._activeNodes[0].get_DomElement());
        centrePos.x = centrePos.x - parseInt(size.width/2);
        centrePos.y = centrePos.y - parseInt(size.height/2);
        
        var animSpeed;
        if (this._activeNodes[0].get_ParentNode() == "")
            animSpeed = this._initialAnimSpeed;
        else
            animSpeed = this._navigateAnimSpeed;
        anim = new AjaxControlToolkit.Animation.MoveAnimation(this._activeNodes[0].get_DomElement(), animSpeed , 30, centrePos.x, centrePos.y, false,"px");
        
        anim.add_ended(this._checkIfBackLinkNeeded);   // We hook this event so that once the node gets moved into the centre, we can
                                                       // execute code to check whether it needs a "back" link provided to move backwards through
                                                       // the heirarchy
        anim.siteMapOwner = this;  // set a reference to this component as it will be lost otherwise when the event is fired. The event
                                   // is fired within the context of the animation, not this component.

        Array.add(destinationAnims,anim);
        
        // Determine how the nodes will be separated according to how many there are. Here we divide the 360 degree circumference of a 
        // circle into smaller segments according to the number of nodes.
        var numNodes = this._activeNodes.length - 1;
        var angleIncrement = 360 / (numNodes+1);
        var browser = this._getBrowserWindowBounds();
        
        // Work out what the radius of the circle should be according to the container
        var radius = 0;
        var proportions;
        var containerConstant = 0;
        if (this._targetContainer == null || this._targetContainer == "")
        {
            proportions = this._getBrowserWindowBounds();
            containerConstant = 4;
        }
        else
        {
            proportions =  $common.getSize($get(this._targetContainer));
            containerConstant = 2;
        }
        radius = Math.min(proportions.width,proportions.height) / containerConstant;
        
        var currentAngle = 0;
        var newPos = new Object();
        // Now go through the remaining nodes and determine the position on the circle they should be using standard geometric
        // calculations to plot around the circumference of a circle. We then define an animation that will initially position these
        // nodes around the centred main node (which is the home node).
        // We add the animation to the destination animation array and continue until no more nodes left.
        for (var i=1; i < this._activeNodes.length; i++)
        {
            var e = this._activeNodes[i].get_DomElement();
            if (e != null)
            {
                currentAngle += angleIncrement;
                newPos.x = radius * Math.cos(currentAngle * Math.PI / 180) + centrePos.x;
                newPos.y = radius * Math.sin(currentAngle * Math.PI / 180) + centrePos.y;
                anim = new AjaxControlToolkit.Animation.MoveAnimation(e, animSpeed, 30, newPos.x, newPos.y, false,"px");
                Array.add(destinationAnims,anim);
            }
        }
        // Only add the handler to the final element's end event.
        anim.add_ended(this._initialAnimEnd);
        anim.siteMapOwner = this;  // set a reference to this component as it will be lost otherwise when the event is fired. The event
                                   // is fired within the context of the animation, not this component.

        // Kick off all the animations so that all our nodes are positioned ready to be clicked on.
        for (var i=0; i < destinationAnims.length; i++)
            destinationAnims[i].play();
    },

    _initialAnimEnd : function(sender, e) {
        /// <summary>
        /// When the animation ends, this event is fired, however, it does not run within the context of the component, bu within the
        /// context of the animation. Here we draw the connecting lines from the parent to the children.    
        /// </summary>
        /// <field name="sender" type="object">
        /// The object initiating this event. Note the use of the SiteMapOwner property. This is not a standard property but is
        /// defined on the object so that it is easy to reference this behavior instance when the event fires.
        /// </field>
        /// <field name="e" type="EventArgs">
        /// Event argument object of the event.
        /// </field>
        sender.siteMapOwner._redrawConnectingLines();
        sender.dispose();
    },
    
    _navigationAnimEnd : function(sender, e) {
        /// <summary>
        /// this handler is to cater for the event when a user clicks on a link that has sub nodes, and that clicked node is animated
        /// towards the centre of the screen. This event is fired once that navigated node is finished moving towards the centre of the
        /// screen and reaches its target position
        /// </summary>
        /// <field name="sender" type="object">
        /// The object initiating this event. Note the use of the SiteMapOwner property. This is not a standard property but is
        /// defined on the object so that it is easy to reference this behavior instance when the event fires.
        /// </field>
        /// <field name="e" type="EventArgs">
        /// Event argument object of the event.
        /// </field>
        sender.siteMapOwner._removeNodesFromDisplay(false);  // remove everything but the selected node
        
        // Now setup the animations for the sub nodes.
        sender.siteMapOwner.constructDomObjects(sender.siteMapOwner.getSelectedNode(),false);
        
        sender.siteMapOwner.setupAnimations();
        sender.dispose();
    },
    
    _checkIfBackLinkNeeded : function(sender, e) {
        /// <summary>
        /// This event is fired when a node is moved into the centre of the selection. That is, this node is a parent of other nodes.
        /// This node may also be a child of other nodes though, so if we can move back through the node heirarchy, we use this event
        /// to provide that option, but only if required.    
        /// </summary>
        /// <field name="sender" type="object">
        /// The object initiating this event. Note the use of the SiteMapOwner property. This is not a standard property but is
        /// defined on the object so that it is easy to reference this behavior instance when the event fires.
        /// </field>
        /// <field name="e" type="EventArgs">
        /// Event argument object of the event.
        /// </field>
        var node = sender.siteMapOwner.getSelectedNode();

        if (node.get_ParentNode() != null && node.get_ParentNode() != "")
        {
            var nodeLoc = $common.getLocation(node.get_DomElement());
            var nodeSize = $common.getSize(node.get_DomElement());

            // Create our back node DOM element
            var e = document.createElement('div');
            e.style.position= "absolute"; 
            
            var backNodeStyle = ""; // this should be a property
            var styleStr = "";
            if (sender.siteMapOwner._backLinkStyle != null && sender.siteMapOwner._backLinkStyle != "" )
            {
                styleStr = "class = '" + sender.siteMapOwner._backLinkStyle + "' "
                Sys.UI.DomElement.addCssClass(e,sender.siteMapOwner._backLinkStyle);
            }
            else
            {
                e.style.backgroundColor = "White";
                e.style.zIndex = 10;  // matches the centre node z-Index
            }
            var backText = "Go Back";  // this should be a culture aware property
            if (sender.siteMapOwner._backLinkText != null && sender.siteMapOwner._backLinkText != "")
                backText = sender.siteMapOwner._backLinkText;
            
            // Note the use of the sender.siteMapOwner.get_id() for the bookmark link. We reference this in the click event so that its easy to
            // get a reference to the main control behavior class/object.    
            e.innerHTML = "<a " + styleStr + " id='" + node.get_id() + "_backDivLink' href='#" + sender.siteMapOwner.get_id() + "'>" + backText + "</a>";
            
            e.id = sender.siteMapOwner.get_id() + "_backDiv";  // set the surround DIV's ID
            e.siteMapOwner = sender.siteMapOwner;
            $addHandler(e,"click",sender.siteMapOwner._backClickHandler);
            document.body.appendChild(e);
            // Position this back node just below the node itself
            var centreNodePos = sender.siteMapOwner._calculateCentrePosition(node.get_DomElement());
            nodeLoc.y += nodeSize.height + 5;
            nodeLoc.x = parseInt(centreNodePos.x - parseInt($common.getSize(e).width / 2));
            $common.setLocation(e,nodeLoc);      // for some reason, secondary animations are not positioned correctly when
        }        
        sender.dispose();   // release our animation
    },
    
    _backClickHandler : function(sender, e) {
        /// <summary>
        /// This event is fired when the user clicks the "back" navigation element of this site map menu control,
        /// if one is present. Here we clear out existing animations and objects, redefine what the centre parent
        /// node is, then call the supporting functions to redraw/animation all the required nodes.
        /// </summary>
        /// <field name="sender" type="object">
        /// The object initiating this event. 
        /// </field>
        /// <field name="e" type="EventArgs">
        /// Event argument object of the event.
        /// </field>
        var cntrlName = sender.target.hash.replace('#','');
        var mainControl = $find(cntrlName);
        var selectedNode = mainControl.getSelectedNode();
        
        mainControl._clearPoints();
        mainControl._removeNodesFromDisplay(true);
        
        var parentNode = mainControl._getNodeByTitle(selectedNode.get_ParentNode());
        mainControl._clearSelectedNode();
        parentNode.set_Selected(true);
        mainControl.constructDomObjects(parentNode,true);  // construct our DOM objects from the root node only
        
        mainControl.setupAnimations();
    },
    
    _handleClick : function(sender,e) {
        /// <summary>
        /// This event is fired when a node is clicked. Here we determine if the node has children. If not, we simply let the
        /// navigation behavior continue and the user will get redirected to their selected page. If the node does have children,
        /// then we make the selected node the parent, and setup the animation to move it to the centre and display its children.
        /// </summary>
        /// <field name="sender" type="object">
        /// The object initiating this event. 
        /// </field>
        /// <field name="e" type="EventArgs">
        /// Event argument object of the event.
        /// </field>
        var id = sender.target.id;
        var node = this._getNodeById(id);

        // The the node clicked has already been selected, this means its in the centre, and we need to move to that page        
        // otherwise we just do the animation and move things around so the newly clicked node becomes selected and in the centre
        if (node != null)   // <-- This can occur if the user actually clicks on the surrounding DIV, and not the link itself.
        {
            if (node.get_Selected())
            {
                return true;
            } else
            {
                if (node.get_HasChildren() == true)
                {
                    sender.preventDefault();
                    // Clear existing selected node and flag the currently clicked one as being selected.
                    this._clearSelectedNode();
                    node.set_Selected(true);

                    var centrePos = this._getCentreOfContainer();

                    // If the node has children, fade out the others and move the selected one into the centre
                    this._clearPoints();
                    for (var i=0; i < this._activeNodes.length; i++)
                    {
                        var anim;
                        if (node == this._activeNodes[i])
                        {
                            if (Sys.UI.DomElement.containsCssClass(node.get_DomElement(), this._leafNodeStyle)) {
                                Sys.UI.DomElement.removeCssClass(node.get_DomElement(), this._leafNodeStyle);
                            }
                            Sys.UI.DomElement.addCssClass(node.get_DomElement(), this._centreNodeStyle);
                            
                            var size =  $common.getSize(node.get_DomElement());
                            centrePos.x = centrePos.x - parseInt(size.width/2);
                            centrePos.y = centrePos.y - parseInt(size.height/2);
                    
                            anim = new AjaxControlToolkit.Animation.MoveAnimation(node.get_DomElement(), this._navigateAnimSpeed, 30, centrePos.x, centrePos.y, false,"px");
                            anim.siteMapOwner = this;
                            anim.add_ended(this._navigationAnimEnd);
                        }
                        else
                            anim = new AjaxControlToolkit.Animation.FadeAnimation(this._activeNodes[i].get_DomElement(), .75, 30, AjaxControlToolkit.Animation.FadeEffect.FadeOut, 0, 1, true);
                        anim.play();
                    }
                }
            }
        }
    },
    
    _getCentreOfContainer : function() {
        /// <summary>
        /// Calculates/returns the centre of the target container where the animated site map will be displayed.
        /// If there is not specified container, then the entire browser window is assumed to be the container.
        /// </summary>
        var centrePos = new Object();
        if (this._targetContainer == null || this._targetContainer == "")
        {
            var browser = this._getBrowserWindowBounds();
            var centrePos = new Object();
            centrePos.x = parseInt(browser.width / 2);
            centrePos.y = parseInt(browser.height / 2);
        } else
        {
            centrePos = this._calculateCentrePosition($get(this._targetContainer));
        }
        
        return centrePos;
    },
    
    _getNodeById : function(id) {
        /// <summary>
        /// Return a node by searching the list for its id
        /// </summary>

        //NOTE: If the containers ID gets passed in here (which is possible) we need to extract the links ID instead and use that
        //      that. So currently, that means removing the '_div' from the end of the string if its present.
        var foundNode = null;
        for (var i=0; i < this._nodeList.length; i++)
        {
            var tmpNode = this._nodeList[i];
            if (tmpNode.get_id() == id)
            {
                foundNode = tmpNode;
                break;
            }
        }
        return foundNode;
    },
    
    _getNodeByTitle : function(title) {
        /// <summary>
        /// Return a node by searching the list for its title
        /// </summary>
        var foundNode = null;
        for (var i=0; i < this._nodeList.length; i++)
        {
            var tmpNode = this._nodeList[i];
            if (tmpNode.get_Title() == title)
            {
                foundNode = tmpNode;
                break;
            }
        }
        return foundNode;
    },

    _redrawConnectingLines : function() {
        /// <summary>
        /// Redraw any lines to connect the parent node to its children.
        /// </summary>
        var centrePos = this._calculateCentrePosition(this._activeNodes[0].get_DomElement());
        for (var i=1; i < this._activeNodes.length; i++)
        {
            var destPoint = this._calculateCentrePosition(this._activeNodes[i].get_DomElement());
            if (this._lineColour == null || this._lineColour == "")
                this._lineColour = "Black";
            this._drawLine(centrePos.x,centrePos.y,destPoint.x,destPoint.y,this._lineColour);    
        }
    },
    
    _calculateCentrePosition : function(element) {
        /// <summary>
        /// This function calculates the centre of the DOM element based on its width and height.
        /// </summary>
        /// <field name="element" type="Sys.UI.DomElement">
        /// The HTML DOM element to calculate the centre position of.
        /// </field>
        var pos = $common.getLocation(element);
        var size = $common.getSize(element);
        pos.x += size.width / 2;
        pos.y += size.height /2 ;
        return pos;
    },
    
    _getBrowserWindowBounds : function() {
        /// <summary>
        /// Gets the bounds (width and height) of the browser window.
        /// Unashamedly ripped off from the Accordian control logic (I think)
        /// </summary>
        var width;
        var height;
            // There are a few ways to get the client size of the browser window
        // and the most appropriate varies from browser to browser...
        if (Sys.Browser.agent != Sys.Browser.Safari) {
            if (document.documentElement && document.documentElement.clientHeight) {
                width = document.documentElement.clientWidth;
                height = document.documentElement.clientHeight;
            } else if (self.innerHeight) {
                width = self.innerWidth;
                height = self.innerHeight;
            } else if (document.body) {
                width = document.body.clientWidth;
                height = document.body.clientHeight;
            }
        } else {
            if (self.innerHeight) {
                width = self.innerWidth;
                height = self.innerHeight;
            } else if (document.documentElement && document.documentElement.clientHeight) {
                width = document.documentElement.clientWidth;
                height = document.documentElement.clientHeight;
            } else if (document.body) {
                width = document.body.clientWidth;
                height = document.body.clientHeight;
            }
        }
        
        return {
            width :  width,
            height : height
        }

    },

    _clearPoints : function() {
        /// <summary>
        /// clear all our points from the screen that have been drawn so far.
        /// </summary>
        for (var i=0; i < this._pointList.length; i++)
            document.body.removeChild(this._pointList[i]);
        this._pointList = new Array();
    },
    
    _clearSelectedNode : function() {
        /// <summary>
        /// Clear the currently selected node from being selected
        /// </summary>
        var node = this.getSelectedNode();
        if (node != null)
            node.set_Selected(false);
    },

    _removeNodesFromDisplay : function(removeSelectedNode) {
        /// <summary>
        /// clears the nodes from the display, optionally removing the selected/centre node as well.    
        /// </summary>
        /// <field name="removeSelectedNode" type="boolean">
        /// If this flag is true, the selected/centre node is also removed.
        /// </field>
        for (var i=0; i < this._activeNodes.length; i++)
        {
            var tmpNode = this._activeNodes[i];
            if (!tmpNode.get_Selected() || (removeSelectedNode && tmpNode.get_Selected()))
            {
            
                $removeHandler(tmpNode.get_DomElement(),"click",this._clickHandler);
                document.body.removeChild(tmpNode.get_DomElement());
            }
        }
        
        // Remove the back link node if it exists
        var backNodeId = this.get_id() + "_backDiv";
        var backNode = $get(backNodeId);
        if (backNode != null)
            document.body.removeChild(backNode);
    },
    
    getSelectedNode : function() {
        /// <summary>
        /// Return/finds the currently selected node.
        /// </summary>
        var selNode = null;
        for (var i=0; i < this._nodeList.length; i++)
        {
            var tmpNode = this._nodeList[i];
            if (tmpNode.get_Selected())
            {
                selNode = tmpNode;
                break;
            }
        }
        return selNode;
    },
    
    _plotPixel : function(x,y,colour) {
        /// <summary>
        /// Plots a "pixel" on the browser window using a 1 pixel DIV, at the given x,y position
        /// and in the colour specified.
        /// </summary>
        /// <field name="x" type="Number" integer="true">
        /// The absolute x position to plot the pixel
        /// </field>
        /// <field name="y" type="Number" integer="true">
        /// The absolute y position to plot the pixel
        /// </field>
        /// <field name="colour" type="String">
        /// The colour of the pixel to plot, in the form of #XXYYZZ
        /// </field>
        var pixel = document.createElement('div');
        pixel.style.position = 'absolute';
        pixel.style.backgroundColor = this._lineColour;
        pixel.style.width = this._lineThickness + 'px';
        pixel.style.height = this._lineThickness + 'px';
        pixel.style.zIndex = 0;
        pixel.style.left = x + 'px';
        pixel.style.top = y + 'px';
        pixel.style.color = this._lineColour;
        document.body.appendChild(pixel);
        Array.add(this._pointList,pixel);
    },
    
    _drawLine: function(x1, y1, x2, y2, colour) {
        /// <summary>
        /// Draw a line on the browser screen, using the PlotPixel function to draw a series of connected points.
        /// </summary>
        /// <field name="x1" type="Number" integer="true">
        /// The X/Left start position of the line.
        /// </field>
        /// <field name="y1" type="Number" integer="true">
        /// The Y/Top start position of the line.
        /// </field>
        /// <field name="x2" type="Number" integer="true">
        /// The X/Left end position of the line.
        /// </field>
        /// <field name="y2" type="Number" integer="true">
        /// The Y/Top end position of the line.
        /// </field>
        /// <field name="colour" type="String">
        /// The colour of the line in the form #XXYYZZ
        /// </field>
        var steep = Math.abs(y2 - y1) > Math.abs(x2 - x1);
        if (steep) {
            t = y1;
            y1 = x1;
            x1 = t;
            t = y2;
            y2 = x2;
            x2 = t;
        }
        var deltaX = Math.abs(x2 - x1);
        var deltaY = Math.abs(y2 - y1);
        var error = 0;
        var deltaErr = deltaY;
        var xStep;
        var yStep;
        var x = x1;
        var y = y1;

        if (x1 < x2) {
            xStep = 1;
        }
        else {
            xStep = -1;
        }

        if(y1 < y2) {
            yStep = 1;
        }
        else {
            yStep = -1;
        }

        if(steep) {
            this._plotPixel(y, x, colour);
        }
        else {
            this._plotPixel(x, y, colour);
        }

        while(parseInt(x) != parseInt(x2)) {
            x = x + xStep;
            error = error + deltaErr;
            if(2 * error >= deltaX) {
                y = y + yStep;
                error = error - deltaX;
            }
            if(steep) {
                this._plotPixel(y, x, colour);
            }
            else {
                this._plotPixel(x, y, colour);
            }
        }
    },

    
    get_InitialAnimationSpeed : function() {
        return this._initialAnimSpeed;
    },
    set_InitialAnimationSpeed : function(value) {
        this.raisePropertyChanged('InitialAnimationSpeed');
        this._initialAnimSpeed = value;
    },
    get_NavigateAnimationSpeed : function() {
        return this._navigateAnimSpeed;
    },
    set_NavigateAnimationSpeed : function(value) {
        this.raisePropertyChanged('NavigateAnimationSpeed');
        this._navigateAnimSpeed = value;
    },
    get_LeafNodeCssClass : function() {
        return this._leafNodeStyle;
    },
    set_LeafNodeCssClass : function(value) {
        this.raisePropertyChanged('LeafNodeCssClass');
        this._leafNodeStyle = value;
    },
    get_CentreNodeCssClass : function() {
        return this._centreNodeStyle;
    },
    set_CentreNodeCssClass : function(value) {
        this.raisePropertyChanged('CentreNodeCssClass');
        this._centreNodeStyle = value;
    },
    get_LinkCssClass : function() {
        return this._linkStyle;
    },
    set_LinkCssClass : function(value) {
        this.raisePropertyChanged('LinkCssClass');
        this._linkStyle = value;
    },
    set_BackLinkCssClass : function(value) {
        this.raisePropertyChanged('BackLinkCssClass');
        this._backLinkStyle = value;
    },
    get_BackLinkCssClass : function() {
        return this._backLinkStyle;
    },
    get_BackLinkText : function() {
        return this._backLinkText;
    },
    set_BackLinkText : function(value) {
        this.raisePropertyChanged('BackLinkText');
        this._backLinkText = value;
    },
    get_LineColour : function() {
        return this._lineColour;
    },
    set_LineColour : function(value) {
        this._lineColour = value;
    },
    get_LineThickness : function() {
        return this._lineThickness;
    },
    set_LineThickness : function(value) {
        this._lineThickness = value;
    },
    get_UrlMap : function() {
        return this._urlMap;
    },
    set_UrlMap : function(value) {
        this.raisePropertyChanged('UrlMap');
        this._urlMap = value;
    },
    get_TargetContainer : function() {
        return this._targetContainer;
    },
    set_TargetContainer : function(value) {
        this.raisePropertyChanged('TargetContainer');
        this._targetContainer = value;
    }
}

AnimatedSiteMapMenu.AnimatedSiteMapMenuBehavior.registerClass('AnimatedSiteMapMenu.AnimatedSiteMapMenuBehavior', Sys.Component);

//************************************
// *** UrlNode Custom Object
//************************************
AnimatedSiteMapMenu.UrlNode = function(id,title,url,parentNode) {
    this._title = title;
    this._url = url;
    this._parentNode = parentNode;
    this._hasChildren = false;
    this._domElement = null;
    this._id = id;
    this._selected = false;
}

AnimatedSiteMapMenu.UrlNode.prototype = {
    get_id : function() {
        return this._id;
    },
    get_Title : function() {
        return this._title;
    },
    get_Url : function() {
        return this._url;
    },
    get_ParentNode : function() {
        return this._parentNode;
    },
    get_HasChildren : function() {
        return this._hasChildren;
    },
    set_HasChildren : function(value) {
        this._hasChildren = value;
    },
    get_DomElement : function() {
        return this._domElement;
    },
    set_DomElement : function(value) {
        this._domElement = value;
    },
    get_Selected : function() {
        return this._selected;
    },
    set_Selected : function(value) {
        this._selected = value;
    }
}

